package domains;

import java.math.BigInteger;

import config.GlobalConfig;

public class Interval {

	private IntervalBound leftBound;
	private IntervalBound rightBound;
	
	public Interval(IntervalBound left,IntervalBound right){
		// TODO: check if this works now
		if(left == null){
			this.leftBound = new IntervalBound(null,true,false);
		} else {
			this.leftBound = left;
		}
		if(right == null){
			this.rightBound = new IntervalBound(null,false,true);
		} else {
			this.rightBound = right;
		}
	}
	
	public IntervalBound getLeft(){
		return this.leftBound;
	}
	
	public IntervalBound getRight(){
		return this.rightBound;
	}
	
	public Interval join(Interval i){
		Interval ret = new Interval(IntervalBound.min(this.leftBound,i.getLeft()),IntervalBound.max(this.rightBound,i.getRight()));
		return ret;
	}
	
	public Interval meet(Interval i){
		IntervalBound lb = IntervalBound.max(this.leftBound,i.getLeft());
		IntervalBound rb = IntervalBound.min(this.rightBound,i.getRight());
		if(lb.le(rb)){
			return new Interval(lb,rb);
		} else {
			if (GlobalConfig.debug) {
				System.out.println("Illegal meet!" + this + " meet with " + i);
			}
			return null;
		}
	}
	
	public Interval widening(Interval i){
		IntervalBound left,right;
		if(this.leftBound.le(i.getLeft())){
			left = this.leftBound;
		} else {
			left = new IntervalBound(null,true,false);
		}
		if(this.rightBound.ge(i.getRight())){
			right = this.rightBound;
		} else {
			right = new IntervalBound(null,false,true);
		}
		return new Interval(left,right);
	}
	
	public Interval narrowing(Interval i){
		IntervalBound left,right;
		IntervalBound minusInf = new IntervalBound(null,true,false);
		IntervalBound plusInf = new IntervalBound(null,false,true);
		
		if(this.leftBound.equals(minusInf)){
			left = i.getLeft();
		} else {
			left = this.leftBound;
		}
		if(this.rightBound.equals(plusInf)){
			right = i.getRight();
		} else {
			right = this.rightBound;
		}
		return new Interval(left,right);
	}
	
	public Interval negate(){
		IntervalBound right = this.leftBound.negate();
		IntervalBound left = this.rightBound.negate();
		return new Interval(left,right);
	}
	
	public Interval add(Interval i){
		IntervalBound left = this.leftBound.add(i.getLeft());
		IntervalBound right = this.rightBound.add(i.getRight());
		return new Interval(left,right);
	}
	
	public Interval subtract(Interval i){
		IntervalBound left = this.leftBound.sub(i.getRight());
		IntervalBound right = this.rightBound.sub(i.getLeft());
		return new Interval(left,right);
	}
	
	public Interval multiply(Interval i){
		IntervalBound l1l2 = this.leftBound.mul(i.getLeft());
		IntervalBound l1u2 = this.leftBound.mul(i.getRight());
		IntervalBound u1l2 = this.rightBound.mul(i.getLeft());
		IntervalBound u1u2 = this.rightBound.mul(i.getRight());
		
		IntervalBound left = IntervalBound.min(l1l2, IntervalBound.min(l1u2,
				IntervalBound.min(u1l2,u1u2)));
		IntervalBound right = IntervalBound.max(l1l2, IntervalBound.max(l1u2,
				IntervalBound.max(u1l2,u1u2)));

		return new Interval(left,right);
	}
	
	public Interval divide(Interval i){
		IntervalBound zero = new IntervalBound(BigInteger.ZERO,false,false);
		IntervalBound left, right;
		
		if(i.getLeft().le(zero) && i.getRight().ge(zero)){
			left = new IntervalBound(null,true,false);
			right = new IntervalBound(null,false,true);
		} else {
		
			IntervalBound l1l2 = this.leftBound.div(i.getLeft());
			IntervalBound l1u2 = this.leftBound.div(i.getRight());
			IntervalBound u1l2 = this.rightBound.div(i.getLeft());
			IntervalBound u1u2 = this.rightBound.div(i.getRight());
			
			left = IntervalBound.min(l1l2, IntervalBound.min(l1u2,
					IntervalBound.min(u1l2,u1u2)));
			right = IntervalBound.max(l1l2, IntervalBound.max(l1u2,
					IntervalBound.max(u1l2,u1u2)));
		}
		
		return new Interval(left,right);
	}
	
	public Interval lt(Interval i){
		IntervalBound l1 = this.leftBound;
		IntervalBound u1 = this.rightBound;
		IntervalBound l2 = i.getLeft();
		IntervalBound u2 = i.getRight();
		
		IntervalBound left,right;
		
		if(u1.lt(l2)){
			left = new IntervalBound(BigInteger.ONE,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		} else if(u2.le(l1)){
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ZERO,false,false);
		} else {
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		}
		return new Interval(left,right);
	}
	
	public Interval eq(Interval i){
		IntervalBound l1 = this.leftBound;
		IntervalBound u1 = this.rightBound;
		IntervalBound l2 = i.getLeft();
		IntervalBound u2 = i.getRight();
		
		IntervalBound left,right;
		
		if(l1.eq(u1) && u1.eq(l2) && l2.eq(u2)){
			left = new IntervalBound(BigInteger.ONE,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		} else if(u1.lt(l2) || u2.lt(l1)){
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ZERO,false,false);
		} else {
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		}
		return new Interval(left,right);
	}
	
	public Interval le(Interval i){
		IntervalBound l1 = this.leftBound;
		IntervalBound u1 = this.rightBound;
		IntervalBound l2 = i.getLeft();
		IntervalBound u2 = i.getRight();
		
		IntervalBound left,right;
		
		if(u1.le(l2)){
			left = new IntervalBound(BigInteger.ONE,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		} else if(u2.lt(l1)){
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ZERO,false,false);
		} else {
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		}
		return new Interval(left,right);
	}
	
	public Interval gt(Interval i){
		IntervalBound l1 = this.leftBound;
		IntervalBound u1 = this.rightBound;
		IntervalBound l2 = i.getLeft();
		IntervalBound u2 = i.getRight();
		
		IntervalBound left,right;
		if(l1.gt(u2)){
			left = new IntervalBound(BigInteger.ONE,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		} else if(l2.ge(u1)){
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ZERO,false,false);
		} else {
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		}
		return new Interval(left,right);
	}
	
	public Interval ge(Interval i){
		IntervalBound l1 = this.leftBound;
		IntervalBound u1 = this.rightBound;
		IntervalBound l2 = i.getLeft();
		IntervalBound u2 = i.getRight();
		
		IntervalBound left,right;
		
		if(l1.ge(u2)){
			left = new IntervalBound(BigInteger.ONE,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		} else if(l2.gt(u1)){
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ZERO,false,false);
		} else {
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		}
		return new Interval(left,right);
	}
	
	public Interval ne(Interval i){
		IntervalBound l1 = this.leftBound;
		IntervalBound u1 = this.rightBound;
		IntervalBound l2 = i.getLeft();
		IntervalBound u2 = i.getRight();
		
		IntervalBound left,right;
		
		if(l1.eq(u1) && u1.eq(l2) && l2.eq(u2)){
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ZERO,false,false);
		} else if(u1.lt(l2) || u2.lt(l1)){
			left = new IntervalBound(BigInteger.ONE,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		} else {
			left = new IntervalBound(BigInteger.ZERO,false,false);
			right = new IntervalBound(BigInteger.ONE,false,false);
		}
		return new Interval(left,right);
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Interval) {
			Interval interval = (Interval) obj;
			return this.getLeft().equals(interval.getLeft()) && this.getRight().equals(interval.getRight());
		}
		return false;
	}
	
	@Override
	public String toString() {
		return "[" + this.leftBound + "," + this.rightBound + "]";
	}
}
